{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "# Python 代码结构\n",
    "\n",
    "## 函数\n",
    "\n",
    "名称到值的绑定提供了有限的抽象手段，通过函数，我们获得了一个更强大的抽象技巧——名称通过它可以绑定到复合操作上，并可以作为一个单元来引用。函数可以接受任何类型的输入作为参数，并返回任何类型的结果。\n",
    "\n",
    "使用函数可以做以下两件事情：\n",
    "\n",
    "* 定义函数\n",
    "* 调用函数\n",
    "\n",
    "函数定义包含`def`语句，它表明了函数名称`<func_name>`和一系列带有名字的形式参数`<formal parameters>`（可选）。之后的语句叫做函数体，`return`（返回）指定了函数的返回表达式`<return expression>`。\n",
    "\n",
    "```python\n",
    "def <func_name>(<formal parameters>):\n",
    "    return <return expression>\n",
    "```\n",
    "\n",
    "第二行必须缩进！返回表达式并不是立即求值，它储存为新定义函数的一部分，并且只在函数最终调用时会被求出。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "def do_noting():\n",
    "    pass\n",
    "do_noting"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "Python函数命名规范和变量命名一样（必须使用字母或者下划线`_`开头，仅能含有字母、数字和下划线）。上面代码示例中的`pass`表明函数没有做任何事情，仅用来进行占位。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "def square(x):\n",
    "    return x * x"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "定义了函数之后，我们使用调用表达式来调用它："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "square(21)  # 441\n",
    "square(2 + 5)  # 49\n",
    "square(square(3))  # 81"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "一个函数可以接受任何数量（包括0）的任何类型的值作为输入变量，并且返回任何数量（包括0）的任何类型的结果。如果函数不显示调用`return`，那么会默认返回`None`。\n",
    "\n",
    "**注意：**关于`None`和`False`的区别，参考教材p.78。\n",
    "\n",
    "### 函数的局部变量\n",
    "\n",
    "函数体的执行会为这个函数的局部变量引入一个新的**命名空间**。所有函数体中的赋值语句（assignment statement），都会把变量名和值存在这个命名空间中。\n",
    "\n",
    "函数体中引用一个变量时，首先查看这个函数的命名空间，如果这个函数定义包裹在其它函数定义中，就依次查看外围函数的命名空间，然后查看**全局**命名空间（也就是函数所属的module的命名空间），最后查看Python的内置类型和变量的命名空间。\n",
    "\n",
    "函数的形参也是存在于函数的局部命名空间中。函数的局部命名空间会在每次调用和返回时进行创建初始化和删除。\n",
    "\n",
    "函数调用的实参传递是通过赋值语句做的，所以传递的是对象的引用。对于类似序列的可变（mutable）类型，如果其作为参数按引用传递给函数，在函数体中改变它的值也会影响它在外围命名空间的值，就像C++中的引用。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "alist = ['a', 'b']\n",
    "def foo(list_obj):\n",
    "    list_obj.append('c')\n",
    "foo(alist)\n",
    "print(alist)\n",
    "\n",
    "count = 1000\n",
    "print('count:', id(count))\n",
    "def incr(n):\n",
    "    print('old n:', id(n))\n",
    "    n += 1\n",
    "    print('new n:', id(n))\n",
    "incr(count)\n",
    "print('count again:', id(count))\n",
    "count"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "### 函数的参数\n",
    "\n",
    "函数在声明参数时大概有下面 4 种形式：\n",
    "\n",
    "1. 位置参数：`def func(a, b): pass`\n",
    "2. 关键字参数：`def func(a, b=1): pass`\n",
    "3. 任意位置参数：`def func(a, b=1, *c): pass`\n",
    "4. 任意关键字参数：`def func(a, b=1, *c, **d): pass`\n",
    "\n",
    "#### 位置参数（无默认值参数）\n",
    "\n",
    "按照顺序将传入参数的值依次复制进去。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "def foo(arg1, arg2):\n",
    "    return arg1, arg2\n",
    "\n",
    "foo('a', 'b')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "使用位置参数必须熟记每个位置参数的含义。\n",
    "\n",
    "#### 关键字参数（有默认值参数）\n",
    "\n",
    "调用函数时可以指定对应参数的名字，可以采用于函数定义不同的顺序调用。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "def foo(arg1='a', arg2='b'):\n",
    "    return arg1, arg2\n",
    "\n",
    "foo(arg2='c', arg1='d')\n",
    "foo(1, 2)\n",
    "foo(1, arg1='a')  # TypeError"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "当位置参数和关键字参数同时存在时，关键字参数必须放到位置参数的后面。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "def foo(arg1='a', arg2):  # SyntaxError\n",
    "    return arg1, arg2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "如果同时使用位置参数和关键字参数两种方式调用函数，关键字参数也必须放到位置参数之后。\n",
    "\n",
    "调用函数时如果没有提供关键字参数的参数值时，将使用函数定义时的默认参数值。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "foo(1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "如果调用函数时提供关键字参数的参数值，则将代替默认值。\n",
    "\n",
    "**注意：**函数的关键字参数的参数值在函数定义时已经计算出来了，而不是在函数运行时。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "num = 1\n",
    "def bar(arg1=num):\n",
    "    print(arg1)\n",
    "num = 2\n",
    "bar()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "所以，在定义函数时，不要把可变的数据类型（列表、字典）当作关键字参数的参数值。函数的关键字参数只会被求值一次，不管函数被怎么调用，当关键字参数的参数值是可变对象时，在函数体中如果其值被改变，再次调用函数时其默认值就是改变后的值。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "xyz_list = ['x', 'y', 'z']\n",
    "def append_xyz(alist, blist=xyz_list):\n",
    "    alist.extend(blist)\n",
    "    return alist\n",
    "append_xyz(['a'])\n",
    "xyz_list.insert(0, 'w')\n",
    "append_xyz(['a'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "def bar(n, alist=[]):\n",
    "    alist.append(n)\n",
    "    return alist\n",
    "\n",
    "print(bar(1))\n",
    "print(bar(2))\n",
    "print(bar(3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "如何避免这种情况？"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "def bar(n, alist=None):\n",
    "    if alist is None:\n",
    "        alist = []\n",
    "    alist.append(n)\n",
    "    return alist\n",
    "\n",
    "print(bar(1))\n",
    "print(bar(2))\n",
    "print(bar(3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "`None`是个内置常量，当然不能被改变，每次函数`bar()`被调用就会用这个值给`alist`赋值。\n",
    "\n",
    "\n",
    "### 任意位置参数\n",
    "\n",
    "任意位置参数可以接受任意数量的位置参数。当参数被用在函数内部时，`*`将一组可变数量的位置参数集合成参数值的元组。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "def concat(*lst, sep='|'):\n",
    "    return sep.join((str(i) for i in lst))\n",
    "print(concat('G', 30, '@', 'Hz', sep=''))  # G30@Hz"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "**注意：**上面的关键词参数必须明确指明，不能通过位置推断。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "print(concat('G', 30, '-'))  # G|30|-, Not G-30"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "通过这种方式给函数传入的所有位置参数都会以元组的形式返回输出："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "def print_args(*args):\n",
    "    print(args)\n",
    "\n",
    "print_args(3, 2, 1, 'a', 'b', ['c', 'd'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "这对于编写接受可变数量的参数的函数非常有用。如果函数同时有限定的位置参数，那么`*args`会收集剩下的参数。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "def print_more(arg1, arg2, *rest_args):\n",
    "    print(arg1)\n",
    "    print(arg2)\n",
    "    print(rest_args)\n",
    "\n",
    "print_more(3, 2, 1, 'a', 'b', ['c', 'd'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "### 任意关键字参数\n",
    "\n",
    "使用`**`可以将参数收集到一个字典中，参数的名字是字典的键，对应参数的值是字典的值。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "def print_kwargs(**kwargs):\n",
    "    print(kwargs)\n",
    "\n",
    "print_kwargs(arg1=1, arg2=2, arg3='a')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "def dconcat(sep=':', **kwargs):\n",
    "    for k in kwargs.keys():\n",
    "        print('{}{}{}'.format(k, sep, kwargs[k]))\n",
    "\n",
    "dconcat(hello='world', python='rocks', sep='~')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "如果想把带有`*args`和`**kwargs`的位置参数混合起来，则它们必须按照顺序出现。\n",
    "### 解包（Unpacking）\n",
    "\n",
    "如果有一个列表或一个字典，我们可以把它直接作为参数传给一个函数，里面的值可以解包出来传给一个个参数。\n",
    "\n",
    "#### 解包为位置实参\n",
    "\n",
    "可以把一个列表或元组解包，对应的值作为位置参数传递，调用的时候要以`*args`的形式："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "lst = [0, 1, 2, 3]\n",
    "print(*lst)  # 注意调用语法为`*args`形式\n",
    "\n",
    "print(*range(5))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "### 解包为关键字实参\n",
    "\n",
    "将字典解包为关键字实参。字典的键作为形参的名字，是字符串，对应键的值为传递的实参。语法上调用的时候以`**kwargs`的形式。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "def f(a, b, c):\n",
    "    print(\"a =\", a, \"b =\", b, \"c =\", c)\n",
    "\n",
    "d = {\"a\":5, \"c\":8, \"b\":2}\n",
    "f(**d)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "另外，Python3.5添加的新特性（[PEP 448](https://www.python.org/dev/peps/pep-0448/)），使得`*args`、`**kwargs`可以在函数参数之外使用："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "a = *range(3), # 这里的逗号不能漏掉\n",
    "print(a)\n",
    "\n",
    "d = {\"hello\": \"world\", \"python\": \"rocks\"}\n",
    "print({**d}[\"python\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "所谓的解包（Unpacking）实际上可以看做是去掉`()`的元组或者是去掉`{}`的字典。这一语法也提供了一个更加 Pythonic 地合并字典的方法："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "user = {'name': \"Google\", 'website': \"https://www.google.com\"}\n",
    "defaults = {'name': \"Anonymous User\", 'page_name': \"Profile Page\"}\n",
    "\n",
    "# 合并字典的3中方法\n",
    "print({**defaults, **user})\n",
    "defaults.update(user)\n",
    "{k:v for d in [user, defaults] for k, v in d.items()}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "### 函数文档字符串\n",
    "在函数体开始的部分附上函数定义说明的文档或加上详细的规范说明。一般建议先用一句话简明扼要的说明函数的功能或作用，然后对函数的参数和返回值进行具体解释，比如参数和返回值的类型及相应的意义，还可以添加函数的用法或者异常处理等内容。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "def fetch_bigtable_rows(big_table, keys, other_silly_variable=None):\n",
    "    \"\"\"Fetches rows from a Bigtable.\n",
    "\n",
    "    Retrieves rows pertaining to the given keys from the Table instance\n",
    "    represented by big_table.  Silly things may happen if\n",
    "    other_silly_variable is not None.\n",
    "\n",
    "    Args:\n",
    "        big_table: An open Bigtable Table instance.\n",
    "        keys: A sequence of strings representing the key of each table row\n",
    "            to fetch.\n",
    "        other_silly_variable: Another optional variable, that has a much\n",
    "            longer name than the other args, and which does nothing.\n",
    "\n",
    "    Returns:\n",
    "        A dict mapping keys to the corresponding table row data\n",
    "        fetched. Each row is represented as a tuple of strings. For\n",
    "        example:\n",
    "\n",
    "        {'Serak': ('Rigel VII', 'Preparer'),\n",
    "         'Zim': ('Irk', 'Invader'),\n",
    "         'Lrrr': ('Omicron Persei 8', 'Emperor')}\n",
    "\n",
    "        If a key from the keys argument is missing from the dictionary,\n",
    "        then that row was not found in the table.\n",
    "\n",
    "    Raises:\n",
    "        IOError: An error occurred accessing the bigtable.Table object.\n",
    "    \"\"\"\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "调用Python的`help()`函数可以打印输出一个函数的文档字符串，得到其参数列表和规范的文档。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "help(fetch_bigtable_rows)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "help(print)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "如果仅仅想得到文档字符串，可以直接查看函数的`__doc__`属性。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "print(fetch_bigtable_rows.__doc__)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "### 第一类对象（first-class object）：函数\n",
    "\n",
    "指可以在执行期创建并作为参数传递给其他函数或传入一个变数的实体。一般第一类对象具有一下特征：\n",
    "\n",
    "1. 可以被存入变量或其他结构\n",
    "2. 可以被作为参数传递给其他方法/函数\n",
    "3. 可以被作为方法/函数的返回值\n",
    "4. 可以在执行期被创建，而无需在设计期全部写出\n",
    "5. 有固定身份\n",
    "\n",
    "“固有身份”是指实体有内部表示，而不是根据名字来识别，比如匿名函数，还可以通过赋值叫任何名字。在Python中，函数/方法都是第一类对象，这对于对函数式编程语言来说是必须的。所以，Python是多范式编程语言。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "def sum_args(*args):\n",
    "    return sum(args)\n",
    "\n",
    "def run_with_positional_args(func, *args):\n",
    "    return func(*args)\n",
    "\n",
    "\n",
    "type(run_with_positional_args)\n",
    "run_with_positional_args(sum_args, 1, 2, 3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "可以把函数作为列表、元组、集合和字典的元素。另外，由于函数名也是不可变的，可以把函数名用作字典的键。\n",
    "\n",
    "### 内部函数\n",
    "\n",
    "在Python中，可以在函数中定义另外一个函数："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "def make_adder(n):\n",
    "    def adder(x):\n",
    "        return x + n\n",
    "    return adder\n",
    "\n",
    "add1 = make_adder(1)\n",
    "add1(2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "另外，当需要在函数内部多次执行复杂的任务时，为了避免循环和代码的堆叠重复，也可以使用内部函数。\n",
    "\n",
    "### 闭包（closure）\n",
    "\n",
    "上面的例子中`make_adder()`函数返回的内部函数可以看作是一个闭包。闭包是一个可以由另外一个函数动态生成的函数，并且可以改变和存储函数外创建的变量的值。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "add1  # <function __main__.make_adder.<locals>.adder>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "add2 = make_adder(2)\n",
    "add2(2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "### lambda函数（匿名函数）\n",
    "\n",
    "如果需要一个函数，但又不想费神地去命名它的时候，可以使用lambda函数（匿名函数，也是闭包）。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "sq = lambda x: x * x\n",
    "sq(2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "(lambda x: x * x)(2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "lambda函数接受一个或多个参数（使用逗号分隔），冒号之后的部分为函数的定义。\n",
    "\n",
    "Python的lambda限制多多，最严重的当属它只能由一条表达式组成。这个限制主要是为了防止滥用，因为当人们发觉lambda很方便，就比较容易滥用，可是用多了会让程序看起来不那么清晰，毕竟每个人对于抽象层级的忍耐/理解程度都有所不同。\n",
    "\n",
    "## 装饰器（decorator）\n",
    "\n",
    "装饰器实质上是一个函数。它把函数作为一个输入并返回另外一个包装（修改）了输入函数之后的函数。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "import time\n",
    "\n",
    "\n",
    "def timethis(func):\n",
    "    \"\"\"\n",
    "    Decorator that reports the execution time of the func.\n",
    "    \"\"\"\n",
    "    def wrapper(*args, **kwargs):\n",
    "        start = time.time()\n",
    "        func_result = func(*args, **kwargs)\n",
    "        end = time.time()\n",
    "        print('function execution time:', end-start)\n",
    "        return func_result\n",
    "    return wrapper\n",
    "\n",
    "def countdown(n):\n",
    "    while n > 0:\n",
    "        n -= 1\n",
    "\n",
    "timethis(countdown)(1000000)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "Python实现了专门的装饰器语法，所以我们可以使用下面的方式来“装饰”一个函数："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "@timethis\n",
    "def countdown(n):\n",
    "    while n > 0:\n",
    "        n -= 1\n",
    "\n",
    "countdown(100000)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "需要强调的是，装饰器并不会修改原始函数的参数以及返回值。使用`*args`和`**kwargs`的目的就是确保任何参数都能适用。而返回结果基本都是调用原始函数`func(*args, **kwargs)`的返回结果，其中`func`就是原始函数。\n",
    "\n",
    "### 带参数的装饰器\n",
    "\n",
    "下面的代码展示了一个Web应用中使用装饰器对用户进行权限检查的例子，当前用户（`current_user`）如果是管理员的话则提醒其无法访问并进行页面重定向，使其跳转到一个指定的页面（通过装饰器函数的参数`endpoint`和任意关键字参数`**values`来控制指定跳转的页面）。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "def staff_redirect(endpoint, **values):\n",
    "    def decorator(func):\n",
    "        def decorated_function(*args, **kwargs):\n",
    "            if current_user.is_staff():   # 检查用户是否为管理员\n",
    "                flash('抱歉，管理用户无法访问。')\n",
    "                return redirect(url_for(endpoint, **values))\n",
    "            return func(*args, **kwargs)\n",
    "        return decorated_function\n",
    "    return decorator\n",
    "\n",
    "\n",
    "@staff_redirect('management_page')\n",
    "@login_required\n",
    "def normal_user_page():\n",
    "    ..."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "上面的代码同时展示了对一个函数使用多个装饰器的例子，其中靠近函数定义的装饰器最先执行，然后依次执行上面的。针对某一函数使用多个装饰器的时候最好清楚装饰器的作用及相互之间的关系，从而确定执行顺序。\n",
    "\n",
    "\n",
    "## 命名空间和作用域\n",
    "\n",
    "上面我们讲到每一个函数都拥有自己的命名空间。每个程序的主要部分定义了全局命名空间，在全局命名空间的变量是全局变量。你可以在一个函数内得到某个全局变量的值并使用它，但如果在函数内部直接对全局变量进行赋值或尝试修改它会引发`UnboundLocalError`异常，因为在函数的命名空间中并不存在这个变量。为了读取全局变量而不是函数中的局部变量，需要在变量前面显示地加关键字`global`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "animal = 'fruitbat'\n",
    "def change_and_print_global():\n",
    "    animal = 'wombat'\n",
    "    print(animal)\n",
    "\n",
    "change_and_print_global()\n",
    "print(animal)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "Python提供两个用于获取命名空间内容的函数：\n",
    "\n",
    "* `locals()`  返回一个局部命名空间内容的字典；\n",
    "* `globals()`  返回一个全局命名空间内容的字典。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "animal = 'fruitbat'\n",
    "def change_local():\n",
    "    animal = 'wombat'\n",
    "    print(locals())\n",
    "change_local()\n",
    "print(globals())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "以两个下划线`__`开头和结尾的名称在Python中有特殊用法，比如一个函数的名称是系统变量`function.__name__`，它的文档字符串是`function.__doc__`。所以，在自定义的变量中最好不要使用`__`。\n",
    "\n",
    "当你打开一个`.py`文件时，经常会在代码的最下面看到`if __name__ == '__main__':`，这里`__name__`是模块（module）的一个内置属性。如果`import`一个模块，那么模块`__name__`的值通常为模块文件名，不带路径或者文件拓展名。但是你也可以像一个标准程序那样直接运行模块，这时候`__name__`的值将是一个特别的缺省名`__main__`。我们可以通过`if __name__ == '__main__'`来判断是否是在直接运行该`.py`文件。我们会在这个判断里面编写直接运行此模块的代码而不用担心此模块被`import`时这些直接运行的代码也被执行。\n",
    "\n",
    "```python\n",
    "#!/usr/bin/env python3.6\n",
    "\n",
    "\"\"\"\n",
    "hello_world.py\n",
    "\"\"\"\n",
    "\n",
    "\n",
    "def main():\n",
    "    print('hello, world!')\n",
    "\n",
    "\n",
    "if __name__ = '__main__':\n",
    "    main()\n",
    "```"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.1"
  },
  "name": "7_function.ipynb",
  "toc": {
   "colors": {
    "hover_highlight": "#ddd",
    "running_highlight": "#FF0000",
    "selected_highlight": "#ccc"
   },
   "moveMenuLeft": true,
   "nav_menu": {
    "height": "358px",
    "width": "252px"
   },
   "navigate_menu": true,
   "number_sections": false,
   "sideBar": true,
   "threshold": 4,
   "toc_cell": false,
   "toc_section_display": "block",
   "toc_window_display": false,
   "widenNotebook": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
